

using System;
using System.Runtime.InteropServices;

namespace CUtil.Win32
{
	#region Win32 interfacing structs
	/// <summary>
	/// This struct maps a Win32-API point into the managed world.
	/// </summary>
	[StructLayout(LayoutKind.Explicit)]
	public struct _Point
	{
		[FieldOffset(0)] public int xPos;
		[FieldOffset(4)] public int yPos;

	}

	/// <summary>
	/// This struct maps the mouse hook struct from the corresponding Win32-API into the managed
	/// world of the .NET envrionment.
	/// </summary>
	[StructLayout(LayoutKind.Explicit)]
	public struct _MouseHookStruct
	{
		[FieldOffset(0)] public CUtil.Win32._Point ptMouse;
		[FieldOffset(4)] public System.IntPtr hWnd;
		[FieldOffset(4)] public System.UInt32 wHitTestCode;
		[FieldOffset(4)] public System.IntPtr dwExtraInfo;
	}
	#endregion

	// Declare a delegate type for processing a book:
	public delegate void MouseNotifyEventHandler(System.Drawing.Point ptMouse);

	/// <summary>
	/// This delegate is used for implementing the callback method for the mouse-hook.
	/// </summary>
	public delegate System.IntPtr MouseHook_Delegate(int nCode, System.IntPtr wParam, ref CUtil.Win32._MouseHookStruct hookStruct);

	/// <summary>
	/// The definition of the different hook types as found in the platform SDK
	/// </summary>
	public struct HookTypes
	{
		internal const int WH_MSGFILTER			= -1;
		internal const int WH_JOURNALRECORD		= 0;
		internal const int WH_JOURNALPLAYBACK	= 1;
		internal const int WH_KEYBOARD			= 2;
		internal const int WH_GETMESSAGE		= 3;
		internal const int WH_CALLWNDPROC		= 4;
		internal const int WH_CBT				= 5;
		internal const int WH_SYSMSGFILTER		= 6;
		internal const int WH_MOUSE				= 7;
		internal const int WH_HARDWARE			= 8;
		internal const int WH_DEBUG				= 9;
		internal const int WH_SHELL				= 10;
		internal const int WH_FOREGROUNDIDLE	= 11;
	}

	/// <summary>
	/// The definition of the different hook codes received by the callback method. These
	/// values are based on the platform SDK.
	/// </summary>
	public struct HookCodes
	{
		internal const int HC_ACTION		= 0;
		internal const int HC_GETNEXT		= 1;
		internal const int HC_SKIP			= 2;
		internal const int HC_NOREMOVE		= 3;
		internal const int HC_SYSMODALON	= 4;
		internal const int HC_SYSMODALOFF	= 5;
	}

	/// <summary>
	/// Hooking the mouse is somewhat more complicated. The operating system (unmanaged world) will call us back
	/// (meaning invoking the hook method) any time, the mouse is moved, pressed, etc. This happens on an event
	/// triggered base and therefore, we need to fix the address of the callback method in the memory space of
	/// the managed world in order to allow us to pass it to the unmamanged world as a callback.
	/// </summary>
	[System.ComponentModel.ToolboxItem(false)]
	public class MouseHook
	{
		#region Win32 DLL imports.
		[DllImport("user32", EntryPoint="SetWindowsHookEx")]
		private static extern System.IntPtr Win32_SetMouseHook(int idHook, MouseHook_Delegate callBackProc, System.IntPtr hMod, System.UInt32 threadID);

		[DllImport("user32", EntryPoint="CallNextHookEx")]
		private static extern System.IntPtr Win32_CallNextHook(int idHookCode, System.IntPtr wParam, ref CUtil.Win32._MouseHookStruct hookStruct);

		[DllImport("user32", EntryPoint="UnhookWindowsHookEx")]
		private static extern bool Win32_UnhookWindowsHook(System.IntPtr hookHandle);

		[DllImport("kernel32", EntryPoint="GetCurrentThreadId")]
		private static extern System.UInt32 Win32_GetCurrentThreadID();
		#endregion

		#region Singleton pattern implementation.
		/// <summary>
		/// This static private member contains the one and only mouse hook object in the application.
		/// </summary>
		private static MouseHook GlobalMouseHook;

		/// <summary>
		/// This static property allows us to retrieve the global mouse hook object.
		/// </summary>
		public static MouseHook MouseHookObject
		{
			get
			{
				if(null == GlobalMouseHook)
				{
					GlobalMouseHook = new MouseHook();
				}
				return(GlobalMouseHook);
			}
		}
		#endregion

		/// <summary>
		/// This static method allows us to set a mouse hook. Note, that the the hook is only application local
		/// and not global to the system.
		/// </summary>
		/// <param name="callBackProc"></param>
		/// <returns></returns>
		public static System.IntPtr SetMouseHook(MouseHook_Delegate callBackProc)
		{
			System.UInt32 threadID = Win32_GetCurrentThreadID();
			return(Win32_SetMouseHook(HookTypes.WH_MOUSE, callBackProc, System.IntPtr.Zero, threadID));
		}

		#region Instance member data.
		/// <summary>
		/// This member variables holds the handle to the mouse hook we injected
		/// into the mouse processing queue of the application.
		/// </summary>
		private System.IntPtr hookHandle;

		/// <summary>
		/// We need to allocate a handle for the callback delegate. This handle needs to be allocated because we pass
		/// the delegate to a Win32-API function that will use it as a callback address.
		/// </summary>
		private System.Runtime.InteropServices.GCHandle gcMouseHookObjectHandle;

		/// <summary>
		/// This event is raised every time a mouse hook event occurs with the HC_ACTION value. The consumer of this
		/// event will receive the current position of the mouse in screen coordinates.
		/// </summary>
		private event MouseNotifyEventHandler MouseNotification;
		#endregion

		/// <summary>
		/// This method raises the event for a mouse notification.
		/// </summary>
		/// <param name="ptMouse">The point of the mouse event. This point contains screen coordinates.</param>
		protected virtual void OnMouseNotification(System.Drawing.Point ptMouse)
		{
			if(null != this.MouseNotification)
			{
				this.MouseNotification(ptMouse);
			}
		}

		/// <summary>
		/// The concept we use here is a little bit different from the event-delegate mechanism used in the .NET
		/// framework. We use a private event and allow a consumer to attach and detach itself by calling the
		/// appropriate method. This is required, because we don't want to hook the mouse all the time. The mouse
		/// hook will only be installed if at least one consumer attaches to the event.
		/// </summary>
		/// <param name="notifyHandler">The notification handler to be attached for mouse hook event notifications.</param>
		public void Attach(MouseNotifyEventHandler notifyHandler)
		{
			this.MouseNotification += notifyHandler;

			if((null != this.MouseNotification) && (System.IntPtr.Zero == this.hookHandle))
			{
				MouseHook_Delegate hookCallBack = new MouseHook_Delegate(this.MouseHookProc);
				this.gcMouseHookObjectHandle = System.Runtime.InteropServices.GCHandle.Alloc(hookCallBack);
				this.hookHandle = SetMouseHook(hookCallBack);
			}
		}

		/// <summary>
		/// The corresponding detach method to the attach method. For more details see the attach method description.
		/// </summary>
		/// <param name="notifyHandler">The notification handler to be detached.</param>
		public void Detach(MouseNotifyEventHandler notifyHandler)
		{
			this.MouseNotification -= notifyHandler;

			if((null == this.MouseNotification) && (System.IntPtr.Zero != this.hookHandle))
			{
				Win32_UnhookWindowsHook(this.hookHandle);
				this.hookHandle = System.IntPtr.Zero;
				this.gcMouseHookObjectHandle.Free();
			}
		}

		/// <summary>
		/// This is the method that will hook into the mouse events of the application.
		/// Note: The thread that calls into this method might not neccessarily be the GUI thread of the application.
		/// </summary>
		/// <param name="nCode">The code that specifies the event that has occured.</param>
		/// <param name="wParam"></param>
		/// <param name="hookStruct"></param>
		/// <returns></returns>
		private System.IntPtr MouseHookProc(int nCode, System.IntPtr wParam, ref CUtil.Win32._MouseHookStruct hookStruct)
		{
			switch(nCode)
			{
				case HookCodes.HC_ACTION:
				{
					OnMouseNotification(new System.Drawing.Point(hookStruct.ptMouse.xPos, hookStruct.ptMouse.yPos));
					break;
				}
			}

			return(Win32_CallNextHook(nCode, wParam, ref hookStruct));
		}
	}
}
